﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Rtc.Collaboration;
//using OotyTest.CallTest.Common;
using Microsoft.Rtc.Signaling;
using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Collaboration.ConferenceManagement;
using BackToBack;

namespace CallOCInteropTest
{
    class B2BTest
    {
        /// <summary>
        /// indicate if we want to test against 2 party or conference mode
        /// </summary>
        public bool IsTwoPartyTest { get; set; }


        private string _agentUri = "sip:richav3@vdomain.com";

        private string AgentUri
        {
            get { return this._agentUri; }
            set { this._agentUri = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        private LocalEndpoint Endpoint { get; set; }
        private AudioVideoCall AudioCall { get; set; }

        private Conversation B2BOutboundConversation { get; set; }
        private AudioVideoCall B2BOutboundCall { get; set; }
        private BackToBackUserAgent B2BUA { get; set; }
        private string B2BPhoneSipUri = "sip:+123123123@vdomain.com;user=phone";
        private bool UseAdhocConference { get; set; }
        private Conference B2BConference { get; set; }
        private string AnynomousUri { get; set; }

        /// <summary>
        /// Constructor of the B2B test.
        /// </summary>
        /// <param name="endpoint"></param>
        public B2BTest(LocalEndpoint endpoint, string destinationUri)
        {
            this.AgentUri = destinationUri;
            this.Endpoint = endpoint;
            this.RegisterEndpointEvents(this.Endpoint);
        }


        /// <summary>
        /// Method to initiate the test. Waits until a call is received (manual test)
        /// </summary>
        public void AVCall_B2BTest()
        {
            this.UseAdhocConference = false;                       


            this.Log(string.Format("Test mode: {0}", this.IsTwoPartyTest ? "TwoParty" : "Conference"));
            this.Log("1. Agent log in on OC, expected agent uir=" + this.AgentUri);
            this.Log(string.Format("2. Call {0} from pstn (or another oc instance)", this.Endpoint.OwnerPhoneUri ?? "null"));
            

            this.Log("--<enter> to exit--");
            Console.ReadLine();
        }


        /// <summary>
        /// This is where the real thing happens when the incoming call for the B2BUA is received.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void HandleAudioVideoCallReceived(object sender, CallReceivedEventArgs<AudioVideoCall> e)
        {
            LocalEndpoint endpoint = sender as LocalEndpoint;

            //initialize the outbound conversation
            this.B2BOutboundConversation = new Conversation(this.Endpoint);
            
            //impersonate when using application endpoint only
            if (this.Endpoint is ApplicationEndpoint)
            {
                this.ImpersonateForB2B();
            }

            this.Log("AudioVideoCall received");
            this.Log(string.Format("IsNewConversation={0}, ID={1}, Pri={2}, subject={3}",
                e.IsNewConversation,
                e.Call.Conversation.Id,
                e.Call.Conversation.Priority,
                e.Call.Conversation.Subject));

            // this is the new incoming call.
            this.AudioCall = e.Call;

            //subscribe to the call's events
            this.RegisterAVCallEvents(this.AudioCall);


            // is it a two-party (P2P) communication test case or are we using a conference as anchor for the agent call.
            if (this.IsTwoPartyTest)
            {
                this.B2BTwoPartyCallTest();
            }
            else
            {
                //choose between ad hoc conference
                if (this.UseAdhocConference)
                {
                    this.Log("creating and joining adhoc conference");
                    ConferenceSession conferenceSession = this.B2BOutboundConversation.ConferenceSession;

                    //join an ad hoc conference
                    conferenceSession.BeginJoin(this.ConferenceJoinCompleted, conferenceSession);
                    Console.WriteLine("b2b'ing it to the conference session");
                }
                else
                {
                    //scheduled conference (recommended)
                    this.ScheduleConference();
                }
            }
        }


        /// <summary>
        // impersonation code (use anonymous when incoming call is received from PSTN/Mediation Server
        /// </summary>
        private void ImpersonateForB2B()
        {
            this.AnynomousUri = ConferenceServices.GenerateAnonymousUri();

            this.B2BOutboundConversation.Impersonate(this.AnynomousUri, this.B2BPhoneSipUri, "foo");
            this.B2BOutboundConversation.Impersonate(this.B2BPhoneSipUri, "", "");
        }
        // Test case with a scheduled conference. The B2BUA will land the call into a scheduled conference
        private void ScheduleConference()
        {
            ConferenceScheduleInformation scheduleInfo = new ConferenceScheduleInformation();
            scheduleInfo.AdmissionPolicy = ConferenceAdmissionPolicy.Anonymous;


            scheduleInfo.IsPasscodeOptional = true;
            scheduleInfo.Passcode = "1234567890";

            this.Endpoint.ConferenceServices.BeginScheduleConference(scheduleInfo,
                delegate(IAsyncResult asyncResult)
                {
                    try
                    {
                        ConferenceServices service = (ConferenceServices)asyncResult.AsyncState;
                        this.B2BConference = service.EndScheduleConference(asyncResult);

                        //kick off the next step
                        this.B2BOutboundConversation.ConferenceSession.BeginJoin(
                            new ConferenceJoinInformation(new RealTimeAddress(this.B2BConference.ConferenceUri)),
                            this.ConferenceJoinCompleted,
                            this.B2BOutboundConversation.ConferenceSession);
                    }
                    catch (RealTimeException rtex)
                    {
                        this.Log("ScheduleConference failed.  ex: " + rtex.ToString());
                    }
                },
                this.Endpoint.ConferenceServices);
        }
        /// <summary>
        /// Complete conference join and dial into the mcu
        /// </summary>
        /// <param name="result">The result of the join operation.</param>
        private void ConferenceJoinCompleted(IAsyncResult result)
        {
            ConferenceSession session = (ConferenceSession)result.AsyncState;

            try
            {
                session.EndJoin(result);
                this.Log("Conference join completed.");

                Call clientCall = this.AudioCall;

                //if its audio, we need to back2back it with the call into the avmcu
                this.B2BOutboundCall = new AudioVideoCall(session.Conversation);


                this.Log("Attempting to Back2Back the caller to the conferencing call...");

                //this is where the BackToBackUserAgent class is used (after the conference was successfully joined)
                this.B2BUA = new BackToBackUserAgent(
                    this.AudioCall,
                    this.B2BOutboundCall);                
                this.B2BUA.BeginParticipate(null, this.B2BUAParticipateCompleted, this.B2BUA);
            }
            catch (RealTimeException rtex)
            {
                this.Log("Conference join failed with ex: " + rtex.ToString());
            }
        }


        /// <summary>
        /// User callback for B2BUA participate; completes the participate and esscalate to conference
        /// </summary>
        /// <param name="asyncResult"></param>
        private void B2BUAParticipateCompleted(IAsyncResult asyncResult)
        {
            try
            {
                BackToBackUserAgent b2bua = (BackToBackUserAgent)asyncResult.AsyncState;
                b2bua.EndParticipate(asyncResult);
                this.Log("AudioVideoCall B2B completed");
                this.Log(string.Format("Inviting agent participant, {0}...", this.AgentUri));
                this.B2BOutboundConversation.BeginInviteRemoteParticipants(new string[] { this.AgentUri }, new ToastMessage("bla"), null, null);
            }
            catch (RealTimeException rtex)
            {
                this.Log("B2BParticipate failed with exception.  Ex: " + rtex.ToString());
            }
        }

        /// <summary>
        /// starts the back2back for two party call
        /// </summary>
        private void B2BTwoPartyCallTest()
        {
            //if its audio, we need to back2back it with the call into the avmcu
            this.B2BOutboundCall = new AudioVideoCall(this.B2BOutboundConversation);
            this.RegisterAVCallEvents(this.B2BOutboundCall);

            this.Log("Attempting to Back2Back the call to: " + this.AgentUri);


            this.B2BUA = new BackToBackUserAgent(
                this.AudioCall,
                this.B2BOutboundCall);
            this.B2BUA.EndParticipate(this.B2BUA.BeginParticipate(
                this.AgentUri,
                null,null));
        }

        /// <summary>
        /// register common endpoint events
        /// </summary>
        /// <param name="endpoint"></param>
        void RegisterEndpointEvents(LocalEndpoint endpoint)
        {
            endpoint.RegisterForIncomingCall<AudioVideoCall>(this.HandleAudioVideoCallReceived);
            endpoint.StateChanged += new EventHandler<LocalEndpointStateChangedEventArgs>(this.HandleEndpointStateChanged);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void HandleEndpointStateChanged(object sender, LocalEndpointStateChangedEventArgs e)
        {
            LocalEndpoint endpoint = sender as LocalEndpoint;
            this.Log(string.Format("endpoint state changed.  {0} --> {1}", e.PreviousState, e.State));
        }

        /// <summary>
        /// subscribe to avcall events
        /// </summary>
        /// <param name="call"></param>
        void RegisterAVCallEvents(AudioVideoCall call)
        {
            //call.CallProgressReceived += HandleCallProgressReceived;
            call.AudioVideoFlowConfigurationRequested += (sender, e) =>
                {
                    string remote = "null";
                    if (call.RemoteEndpoint != null)
                    {
                        remote = call.RemoteEndpoint.Participant.DisplayName;
                    }

                    this.Log(string.Format("[remote={0}] Flow created", remote));
                };
            call.StateChanged += (sender, e) =>
                {
                    string remote = "null";
                    if (call.RemoteEndpoint != null)
                    {
                        remote = call.RemoteEndpoint.Participant.DisplayName;
                    }

                    this.Log(string.Format("[remote={0}] Call state changed.  {1} --> {2}, Reason={3}", 
                        remote, 
                        e.PreviousState, 
                        e.State, 
                        e.TransitionReason));
                };
        }

        void Log(string message)
        {
            Console.WriteLine("[B2BTest] " + message);
        }
    }
}
